home *** CD-ROM | disk | FTP | other *** search
/ Mac Magazin/MacEasy 19 / Mac Magazin and MacEasy Magazine CD - Issue 19.iso / Musik & Kunst / Ear Workout 2.1 / source code / ear_sing_interval.cp < prev    next >
Text File  |  1996-01-15  |  17KB  |  528 lines

  1.  
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <stdio.h>
  5. #include <math.h>
  6.  
  7. #include <OSUtils.h>
  8. #include <QuickDraw.h>
  9. #include <Sound.h>
  10. #include <SoundInput.h>
  11.  
  12. #define NEED_MAC_STUFF 1
  13.  
  14. #include "Ninkasi:C++ util:generic.h"
  15. #include "Ninkasi:C++ util:complete_window.h"
  16. #include "ear_defines.h"
  17. #include "ear_decl.h"
  18. #include "ear_prototypes.h"
  19.  
  20. DEN_MOTHER_T interval_den_mother;
  21. void find_actual_sample(UBYTE_T **where,ULONG_T *nbytes,Handle snd_handle);
  22.  
  23. extern double sin(),cos(),exp(),log(),fabs(),sqrt();
  24. void name_of_interval(char *name,int interval);
  25.  
  26. #define MAX_SAMPLE_SIZE 100000        
  27.  
  28. #define MIN_THRESHOLD 7
  29.     // average abs val of deviation from mean of their sampled singing 
  30.     // should be at least this much
  31.  
  32.     int
  33. get_period(
  34.     unsigned char *data,    //-- your data
  35.     int n,            //-- number of data points
  36.     int lo_p,        //-- shortest possible period
  37.     int hi_p,        //-- longest possible period
  38.     short *grid,        //-- storage you allocate for me (call me with n=0
  39.                 //    to find out how many bytes)
  40.     int grid_space,        //-- number of bytes you allocated for me
  41.     int *naverage,        //-- number of periods averaged together to get this
  42.                 //    result (output)
  43.     short **nhit_ptr,    //--pointer to the histogram; 256 bins per octave;
  44.                 //    bin number=256*log2(p/lo_p)
  45.     int *nhist,        //--tells them how many bins in histogram,
  46.     int **int_info_handle,    //--Passes back a pointer to some handy data:
  47.                 //    *int_info[0]=mean abs. val of deviation from mean
  48.                 //    *int_info[1]=mean abs. val of derivative
  49.                 //    (Means are only based on a subset of the
  50.                 //    data.)
  51.                 //    *int_info[2]=xshift
  52.                 //    *int_info[3]=yshift
  53.                 //    *int_info[4]=tstep
  54.     double **double_info_handle
  55.                 //
  56.     );
  57.  
  58. int closest_integer(double x);
  59.         
  60.     void
  61. sing_interval_den_mother(complete_window *my_complete_window)
  62.     {
  63.       //--- decoder string for controls:
  64.       static char *decoder_ptr =
  65.  
  66. "\0request,result,play,record,go_on,t1,t2,t3";
  67.         //--- has to begin with null so subroutines know it's not really
  68.         //    a Pascal string
  69.         
  70.       static char **decoder_string = &decoder_ptr;
  71.  
  72.       //--- keeping track of my current state:
  73.       static int first_time = 1;
  74.       static int they_have_interval_to_consider = 0;
  75.  
  76.       #define TYPICAL_TEMPO 105
  77.       static double typical_tempo = TYPICAL_TEMPO;
  78.       static int mean_note = 66;
  79.       static int interval_enabled[MAX_INTERVAL+1];
  80.       //--- about the current interval:
  81.       static int note1,note2,duration,rest_between,interval,center,abs_i;
  82.       static int lower_note = -999;
  83.       static int higher_note = -999;
  84.       
  85.       //--- about their sampled singing:
  86.       static int have_sample = 0;
  87.       static OSErr snd_record_result;
  88.       static double raw_sampling_freq = 22254.54545;
  89.       static double sampling_freq;
  90.       static int nbytes;
  91.       static UBYTE_T *where_is_actual_sample;
  92.       static int interval_they_sang;
  93.       static double amount_sharp_or_flat;
  94.         // The following are globals, so wrap_it_up() can deallocate the sample:
  95.         //    int have_sample_storage = 0;
  96.         //    static Handle sound_handle = (Handle) 0;
  97.  
  98.       static int have_analysis = 0;
  99.       static int period;
  100.       double freq,note;
  101.       int *period_int_info;
  102.       double *period_double_info;
  103.       static int have_storage_for_get_period = 0;
  104.       static short **get_period_storage_handle;
  105.       static int size_of_get_period_storage;
  106.       static short *period_hist;
  107.       static int nperiod_hist;
  108.       static int analysis_succeeded;
  109.       
  110.       int need_to_redraw,need_to_redraw_all,play_it,ready_to_update,record_it,
  111.           redraw_request,they_want_to_record,just_gave_them_interval_to_consider,
  112.           they_hit_go_on;
  113.       
  114.       static int difficulty_of_interval[MAX_INTERVAL+1]=
  115.           {0, 1,1, 1,1, 1, 3,1, 2,2, 3,3, 0, 3,3, 3       };
  116.          //  1   2    3   4   5    6    7   8   9   10
  117.          
  118.       static int difficulty = 3;
  119.       
  120.       static int need_to_analyze = 0;
  121.       static int need_to_tell_them_what_they_sang = 0;
  122.  
  123.       
  124.       char nifty_name[100];
  125.       int nifty_index,recognized;
  126.       GrafPtr save_graf;
  127.       double tempo;
  128.       int min_duration,max_duration;
  129.  
  130.  
  131.       need_to_redraw = 0;
  132.       need_to_redraw_all = 0;
  133.       redraw_request = 0;
  134.       play_it = 0;
  135.       they_want_to_record = 0;
  136.       just_gave_them_interval_to_consider = 0;
  137.       they_hit_go_on = 0;
  138.       
  139.       if (first_time) {
  140.         first_time = 0;
  141.             for (int i=0; i<=MAX_INTERVAL; i++) {
  142.               interval_enabled[i] = (i!=0) && (i<=12);
  143.             }
  144.       }
  145.             
  146.       switch(my_complete_window->whassup) {
  147.         case complete_window_created:
  148.           need_to_redraw = 1;
  149.           need_to_redraw_all = 1;
  150.           break;
  151.         case complete_window_redraw:
  152.           need_to_redraw = 1;
  153.           need_to_redraw_all = 1;
  154.           ready_to_update = 1; //-- main program does begin update & sets graf port
  155.           break;
  156.         case complete_window_action:
  157.           part_number_to_nifty_label(nifty_name,&nifty_index,
  158.             *decoder_string,my_complete_window->part_number);
  159.           if (nifty_index != -999) {
  160.             recognized = 0;
  161.             if (strcmp(nifty_name,"play")==0) {
  162.               play_it = 1;
  163.             }
  164.             if (strcmp(nifty_name,"record")==0) {
  165.               they_want_to_record = 1;
  166.             }
  167.             if (strcmp(nifty_name,"go_on")==0) {
  168.               they_hit_go_on = 1;
  169.             }
  170.           }//--end if they hit a valid control
  171.           break;
  172.         case complete_window_erase:
  173.           they_have_interval_to_consider = 0;
  174.           first_time = 1;
  175.           sing_interval_window_exists = 0;
  176.           if (have_sample_storage) {
  177.             if (VALID_HANDLE(sound_handle)) {
  178.               HUnlock(sound_handle);
  179.               DisposHandle(sound_handle);
  180.             }
  181.             have_sample_storage = 0;
  182.           }
  183.           return;
  184.       }
  185.  
  186.       if (they_hit_go_on)
  187.         they_have_interval_to_consider = 0;
  188.         
  189.       if (!they_have_interval_to_consider) {
  190.         int previous_lower,previous_higher;
  191.         previous_lower = lower_note;
  192.         previous_higher = higher_note;
  193.         min_duration = .7 * 2000.*60./typical_tempo;
  194.         max_duration = 4 * 2000.*60./typical_tempo;
  195.         do {
  196.               tempo = typical_tempo*(1.+.2*(random_double()+random_double()-1.));
  197.               duration = 2000.*60./tempo;
  198.             } while (duration>max_duration || duration<min_duration);
  199.         if (difficulty<1) difficulty=1; // avoid infinite loop...
  200.         do {
  201.           abs_i = random_integer(MAX_INTERVAL+1)-1;
  202.           if (random_double()<.5)
  203.             interval = abs_i;
  204.           else
  205.             interval = -abs_i;
  206.           center = mean_note+(random_double()-.5)*8+(random_double()-.5)*8;
  207.                         /* mean_note plus or minus an something, biased towards
  208.                            pitches close to mean_note */
  209.           lower_note = center-interval/2;
  210.           higher_note = lower_note+interval;
  211.           if (interval<0) {
  212.             note1 = higher_note;
  213.             note2 = lower_note;
  214.           }
  215.           else {
  216.             note1 = lower_note;
  217.             note2 = higher_note;
  218.           }
  219.         } while (random_integer(difficulty)<difficulty_of_interval[abs_i]
  220.             || (previous_lower==lower_note && previous_higher==higher_note));
  221.         play_it = 1;
  222.         they_have_interval_to_consider = 1;
  223.         just_gave_them_interval_to_consider = 1;
  224.         need_to_redraw = 1;
  225.         redraw_request = 1;
  226.         have_analysis = 0;
  227.       }//---end if need to choose new interval
  228.  
  229.       //--- This is the first block of drawing code, including the code
  230.       //    that tells them what interval to sing.  There is a second block,
  231.       //    which includes the code that tells them what they actually sang.
  232.       if (need_to_redraw) {
  233.         if (!ready_to_update) {
  234.           GetPort(&save_graf);
  235.           SetPort(my_complete_window->the_window);
  236.         }
  237.         if (need_to_redraw_all) {
  238.           normal_text_style();
  239.           refresh_text(my_complete_window,"t1",1,*decoder_string);
  240.           refresh_text(my_complete_window,"t2",1,*decoder_string);
  241.           TextFont((short) newYork);
  242.           TextSize((short) 10);
  243.           refresh_text(my_complete_window,"t3",1,*decoder_string);
  244.           normal_text_style();
  245.         }
  246.         if (redraw_request || need_to_redraw_all) {
  247.           Handle h;
  248.           char s[50],interval_name[30];
  249.           int i,tot;
  250.           get_item_by_nifty_label(my_complete_window->the_window,
  251.                   "request",1,*decoder_string,&h);
  252.           if (VALID_HANDLE(h)) {
  253.             name_of_interval(interval_name,abs_i);
  254.             if (abs_i != 0)
  255.               sprintf(s+1,"%s %s",(interval<0 ? "descending" : "ascending"),
  256.                     interval_name);
  257.             else
  258.               sprintf(s+1,"%s",interval_name);
  259.             s[0] = strlen(s+1);
  260.             TextFont((short) newYork);
  261.             SetIText(h,(unsigned char *) s);
  262.             normal_text_style();
  263.           }
  264.         }
  265.         if (!ready_to_update) {
  266.           SetPort(save_graf);
  267.         }
  268.       } //---end if need to redraw
  269.       
  270.       
  271.       if (play_it) {
  272.         int notes[4];
  273.         notes[0] = note1;
  274.         play_chord(1,my_snd_chan,notes,duration,1);
  275.       }
  276.       
  277.       if (they_want_to_record 
  278.           //  || just_gave_them_interval_to_consider
  279.           //        ...made it impossible to get out of modal dialog
  280.           ) {
  281.         Point corner;
  282.         ULONG_T i;
  283.         if (have_sample_storage) {
  284.           if (VALID_HANDLE(sound_handle)) {
  285.             HUnlock(sound_handle);
  286.             DisposHandle(sound_handle);
  287.           }
  288.           have_sample_storage = 0;
  289.         }
  290.         sound_handle = NewHandle((Size) MAX_SAMPLE_SIZE);
  291.         if (sound_handle == (Handle) 0) {
  292.           static char s[75];
  293.           sprintf(s,"Out of memory, recording sound, error=%d",
  294.               (int) MemError());
  295.           bail_out(s);
  296.         }
  297.         else
  298.           have_sample_storage = 1;
  299.         SetPt(&corner,300,100);
  300.         HUnlock(sound_handle);
  301.         snd_record_result = SndRecord((ModalFilterProcPtr) 0,corner,siBestQuality,&sound_handle);
  302.         HLock(sound_handle);
  303.         if (snd_record_result == userCanceledErr) SysBeep((short) 0);
  304.         if (snd_record_result==0) {
  305.           ULONG_T nbytes_raw;
  306.           have_sample = 1;
  307.           find_actual_sample(&where_is_actual_sample,&nbytes_raw,sound_handle);
  308.  
  309.           if (0) { //---- test with sine wave
  310.             int i;
  311.             watch_cursor();
  312.             for (i=0; i<nbytes_raw; i++)
  313.               where_is_actual_sample[i] = 128+60*sin(.4+2.*3.1416*440./raw_sampling_freq*i);
  314.             arrow_cursor();
  315.           }
  316.           if (0) { //---- test with zero
  317.             int i;
  318.             for (i=0; i<nbytes_raw; i++)
  319.               where_is_actual_sample[i] = 128;
  320.           }
  321.  
  322.           nbytes = nbytes_raw;
  323.           sampling_freq = raw_sampling_freq;
  324.         
  325.         }
  326.         if (have_sample) {
  327.           need_to_analyze = 1;
  328.           need_to_tell_them_what_they_sang = 1;
  329.           need_to_redraw = 1;
  330.         }
  331.           }
  332.  
  333.       if (need_to_analyze && have_sample) {
  334.             int lo_p,hi_p,naverage;
  335.         if (!have_storage_for_get_period) {
  336.           size_of_get_period_storage = get_period((unsigned char *) 0,
  337.                 0,0,0,(short *) 0,0,(int *) 0,&period_hist,&nperiod_hist,
  338.                 (int **) 0,(double **) 0);
  339.           get_period_storage_handle = (short **) NewHandle((Size) size_of_get_period_storage);
  340.           if (VALID_HANDLE(get_period_storage_handle)) {
  341.             have_storage_for_get_period = 1;
  342.           }
  343.           else
  344.             bail_out("out of memory");
  345.         }
  346.             lo_p = 20; 
  347.             hi_p = 300;
  348.         watch_cursor();
  349.         period = get_period(where_is_actual_sample,(int) nbytes,
  350.                 lo_p,hi_p,
  351.               (short *) *get_period_storage_handle,
  352.               size_of_get_period_storage,
  353.               &naverage,&period_hist,&nperiod_hist,
  354.               &period_int_info,&period_double_info);
  355.         arrow_cursor();
  356.         analysis_succeeded = 
  357.             (period!=0) && (period_int_info[0]>=MIN_THRESHOLD);
  358.         if (analysis_succeeded) {
  359.           freq = 256.*raw_sampling_freq/period;
  360.           note = 60.+12.*log(freq/261.6)/log(2.);
  361.           interval_they_sang = closest_integer(note-note1);
  362.           amount_sharp_or_flat = note-note1-interval_they_sang;
  363.         }
  364.         have_analysis = analysis_succeeded;
  365.         need_to_analyze = 0;
  366.       }
  367.  
  368.       //--- This is the second block of drawing code, including the code
  369.       //    that tells them what they actually sang.
  370.       if (need_to_redraw) {
  371.         if (!ready_to_update) {
  372.           GetPort(&save_graf);
  373.           SetPort(my_complete_window->the_window);
  374.         }
  375.         if (need_to_tell_them_what_they_sang || need_to_redraw_all) {
  376.           Handle h;
  377.           char s[300],s2[300],interval_name[300],inverted_interval_name[300];
  378.           int i,tot;
  379.           get_item_by_nifty_label(my_complete_window->the_window,
  380.                   "result",1,*decoder_string,&h);
  381.           if (VALID_HANDLE(h)) {
  382.             if (have_sample) {
  383.               if (have_analysis) {
  384.                 name_of_interval(interval_name,interval_they_sang);
  385.                 // If they sang an interval in the opposite direction to what
  386.                 // was requested, maybe they sang the right note, but displaced
  387.                 // an octave.  Tell them what the inversion would be:
  388.                 if (interval*interval_they_sang<0) {
  389.                   name_of_interval(inverted_interval_name,
  390.                               inversion_of_interval(interval_they_sang));
  391.                   sprintf(s2," (inversion would be %s)",inverted_interval_name);
  392.                 }
  393.                 else
  394.                   sprintf(s2,"");
  395.                 sprintf(s+1,"%s %s, %4.1lf semitones %s%s",
  396.                 (interval_they_sang==0 ? "" : 
  397.                     (interval_they_sang<0 ? "descending" : "ascending")),
  398.                 interval_name,
  399.                 fabs(amount_sharp_or_flat),
  400.                 (amount_sharp_or_flat>0 ? "sharp" : "flat"),
  401.                 s2
  402.                 
  403.                 /*
  404.                 (int) period,(double) freq,(int) note1,(double) note,
  405.                 
  406.                 period_int_info[0],period_int_info[1],
  407.                 period_int_info[2],period_int_info[3],
  408.                 period_int_info[4]
  409.                 
  410.                 */
  411.                 
  412.                 );
  413.                 need_to_tell_them_what_they_sang = 0;
  414.               }
  415.               else {
  416.                 if (period_int_info[0]<1)
  417.                   sprintf(s+1,"No sound was detected.  You may not have a microphone installed.");
  418.                 else
  419.                   sprintf(s+1,"Try singing louder, or closer to the microphone.");
  420.               }
  421.             }
  422.             else
  423.               sprintf(s+1,""); //-- don't have any analysis, haven't recorded a sample
  424.             s[0] = strlen(s+1);
  425.             TextFont((short) newYork);
  426.             SetIText(h,(unsigned char *) s);
  427.             normal_text_style();
  428.           }
  429.         }
  430.         if (!ready_to_update) {
  431.           SetPort(save_graf);
  432.         }
  433.       } //---end if need to redraw
  434.  
  435.  
  436.     }
  437.  
  438.     int
  439. closest_integer(double x)
  440.     {
  441.       int y;
  442.       y = x+.5;
  443.       while(fabs(y+1.-x)<fabs(y-x)) ++y;
  444.       while(fabs(y-1.-x)<fabs(y-x)) --y;
  445.       return y;
  446.     }
  447.  
  448.     void
  449. name_of_interval(char *interval_name,int interval)
  450.     {
  451.       int abs_i,octave_bump;
  452.       abs_i = interval;
  453.       if (abs_i<0) abs_i = -abs_i;
  454.       octave_bump = 0;
  455.       if (abs_i>15) {
  456.         while(abs_i>=12) {
  457.           abs_i -= 12;
  458.           ++octave_bump;
  459.         }
  460.       }
  461.             switch(abs_i) {
  462.               case 0: strcpy(interval_name,"unison"); break;
  463.               case 1: strcpy(interval_name,"b2"); break;
  464.               case 2: strcpy(interval_name,"2"); break;
  465.               case 3: strcpy(interval_name,"b3"); break;
  466.               case 4: strcpy(interval_name,"3"); break;
  467.               case 5: strcpy(interval_name,"4"); break;
  468.               case 6: strcpy(interval_name,"b5"); break;
  469.               case 7: strcpy(interval_name,"5"); break;
  470.               case 8: strcpy(interval_name,"b6"); break;
  471.               case 9: strcpy(interval_name,"6"); break;
  472.               case 10: strcpy(interval_name,"b7"); break;
  473.               case 11: strcpy(interval_name,"7"); break;
  474.               case 12: strcpy(interval_name,"octave"); break;
  475.               case 13: strcpy(interval_name,"b9"); break;
  476.               case 14: strcpy(interval_name,"9"); break;
  477.               case 15: strcpy(interval_name,"b10"); break;
  478.               default: strcpy(interval_name,"--"); break;
  479.             }
  480.       
  481.       if (octave_bump>0) {
  482.         char s[100];
  483.         switch(octave_bump) {
  484.           case 1:
  485.             strcat(interval_name," + octave");
  486.             break;
  487.           default:
  488.             sprintf(s," + %d octaves",octave_bump);
  489.             strcat(interval_name,s);
  490.             break;
  491.         }
  492.       }
  493.     }
  494.  
  495. #define UBYTE_AT(x) (* (UBYTE_T *) (x))
  496. #define UWORD_AT(x) (* (UWORD_T *) (x))
  497. #define ULONG_AT(x) (* (ULONG_T *) (x))
  498.  
  499.  
  500.     // Returns the address of the actual sound data, yes the actual honest
  501.     // to god bytes, not the header in front or the header in front of the
  502.     // header.  If there's more than one sound command, will only return
  503.     // the data correspinding to the first one.
  504.     // Although we don't move memory in here, it's probably safest to
  505.     // lock the sample before calling this routine.
  506.     void
  507. find_actual_sample(UBYTE_T **where,ULONG_T *nbytes,Handle snd_handle)
  508.     {
  509.       UWORD_T nsynth,ncmds,cmd;
  510.       ULONG_T par2;
  511.       UBYTE_T *find_it;
  512.       UBYTE_T *snd;
  513.       snd = * (UBYTE_T **) snd_handle;
  514.       nsynth = UWORD_AT(snd+2);
  515.       ncmds = UWORD_AT(snd+4+nsynth*2+4);
  516.       cmd = UWORD_AT(snd+4+nsynth*2+4+2);
  517.       par2 = ULONG_AT(snd+4+nsynth*2+4+2+4);
  518.       if (cmd & dataOffsetFlag)
  519.         find_it = snd+par2;
  520.       else
  521.         find_it = * (UBYTE_T **) &par2;
  522.       find_it += 4;  //---- THINK Reference seems to leave out a longword,
  523.                // so bump past it!?!?
  524.       *nbytes = ULONG_AT(find_it);
  525.       *where = find_it + 4*4+2;
  526.     }
  527.  
  528.